package org.quincy.rock.core.dao.sql;

import java.util.ArrayList;
import java.util.List;

import org.quincy.rock.core.dao.NameInterceptor;
import org.quincy.rock.core.dao.ValueInterceptor;
import org.quincy.rock.core.exception.UnsupportException;
import org.quincy.rock.core.lang.DataType;
import org.quincy.rock.core.util.CoreUtil;
import org.quincy.rock.core.util.StringUtil;

/**
 * <b>Predicate。</b>
 * <p><b>详细说明：</b></p>
 * <!-- 在此添加详细说明 -->
 * 无。
 * @version 1.0
 * @author mex2000
 * @since 1.0
 */
public class Predicate extends OpExpression {

	/**
	 * serialVersionUID。
	 */
	private static final long serialVersionUID = -1365217878714523355L;

	private Boolean not;

	private List<Predicate> predicates;

	private List<Condition> conditions;

	@Override
	public Op op() {
		Op op = super.op();
		return op == null ? Op.AND : op;
	}

	@SuppressWarnings("unchecked")
	@Override
	public Predicate op(Op op) {
		super.op(op);
		return this;
	}

	public Boolean getNot() {
		return Boolean.TRUE.equals(not);
	}

	public void setNot(Boolean not) {
		this.not = not;
	}

	public List<Predicate> getPredicates() {
		if (predicates == null)
			predicates = new ArrayList<>();
		return predicates;
	}

	public void setPredicates(List<Predicate> predicates) {
		this.predicates = predicates;
	}

	public Predicate addPredicate(Predicate p) {
		this.getPredicates().add(p);
		return this;
	}

	public List<Condition> getConditions() {
		if (conditions == null)
			conditions = new ArrayList<>();
		return conditions;
	}

	public void setConditions(List<Condition> conditions) {
		this.conditions = conditions;
	}

	public Predicate addCondition(Condition c) {
		this.getConditions().add(c);
		return this;
	}

	public Predicate isNull(String name) {
		return this.addCondition(Condition.isNull(name));
	}

	public Predicate isNotNull(String name) {
		return this.addCondition(Condition.isNotNull(name));
	}

	public Predicate like(Match match, String name, String value) {
		return this.addCondition(Condition.like(match, name, value));
	}

	public Predicate notLike(Match match, String name, String value) {
		return this.addCondition(Condition.notLike(match, name, value));
	}

	public Predicate like(String name, String value) {
		return like(Match.FULL, name, value);
	}

	public Predicate notLike(String name, String value) {
		return notLike(Match.FULL, name, value);
	}

	public Predicate between(DataType type, String name, Object value1, Object value2) {
		return this.addCondition(Condition.between(type, name, value1, value2));
	}

	public Predicate notBetween(DataType type, String name, Object value1, Object value2) {
		return this.addCondition(Condition.notBetween(type, name, value1, value2));
	}

	public Predicate between(String name, Object value1, Object value2) {
		return this.addCondition(Condition.between(DataType.of(value1), name, value1, value2));
	}

	public Predicate notBetween(String name, Object value1, Object value2) {
		return this.addCondition(Condition.notBetween(DataType.of(value1), name, value1, value2));
	}

	public Predicate in(DataType type, String name, Object... values) {
		return this.addCondition(Condition.in(type, name, values));
	}

	public Predicate notIn(DataType type, String name, Object... values) {
		return this.addCondition(Condition.notIn(type, name, values));
	}

	public Predicate in(String name, Object... values) {
		return this.addCondition(Condition.in(DataType.of(values[0]), name, values));
	}

	public Predicate notIn(String name, Object... values) {
		return this.addCondition(Condition.notIn(DataType.of(values[0]), name, values));
	}

	public Predicate op(Op op, DataType type, String name, Object value) {
		return this.addCondition(Condition.of(op, type, name, value));
	}

	public Predicate equal(DataType type, String name, Object value) {
		return op(Op.EQUAL, type, name, value);
	}

	public Predicate equal(String name, Object value) {
		return op(Op.EQUAL, DataType.of(value), name, value);
	}

	public Predicate notEqual(DataType type, String name, Object value) {
		return op(Op.NOTEQUAL, type, name, value);
	}

	public Predicate notEqual(String name, Object value) {
		return op(Op.NOTEQUAL, DataType.of(value), name, value);
	}

	public Predicate greater(DataType type, String name, Object value) {
		return op(Op.GREATER, type, name, value);
	}

	public Predicate greater(String name, Object value) {
		return op(Op.GREATER, DataType.of(value), name, value);
	}

	public Predicate greaterEqual(DataType type, String name, Object value) {
		return op(Op.GREATEREQUAL, type, name, value);
	}

	public Predicate greaterEqual(String name, Object value) {
		return op(Op.GREATEREQUAL, DataType.of(value), name, value);
	}

	public Predicate less(DataType type, String name, Object value) {
		return op(Op.LESS, type, name, value);
	}

	public Predicate less(String name, Object value) {
		return op(Op.LESS, DataType.of(value), name, value);
	}

	public Predicate lessEqual(DataType type, String name, Object value) {
		return op(Op.LESSEQUAL, type, name, value);
	}

	public Predicate lessEqual(String name, Object value) {
		return op(Op.LESSEQUAL, DataType.of(value), name, value);
	}

	@Override
	public StringBuilder build(StringBuilder buffer, NameInterceptor nameInterceptor,
			ValueInterceptor valueInterceptor) {
		if (!isEmpty()) {
			if (getNot())
				buffer.append("NOT (");
			String op = getOp();
			getConditions().forEach(condi -> {
				buffer.append(StringUtil.CHAR_SPACE);
				condi.build(buffer, nameInterceptor, valueInterceptor);
				buffer.append(StringUtil.CHAR_SPACE).append(op);
			});
			getPredicates().forEach(pred -> {
				buffer.append(StringUtil.CHAR_SPACE).append(StringUtil.CHAR_LEFT_CURVE);
				pred.build(buffer, nameInterceptor, valueInterceptor);
				buffer.append(StringUtil.CHAR_RIGHT_CURVE).append(StringUtil.CHAR_SPACE).append(op);
			});
			buffer.setLength(buffer.length() - op.length() - 1);
			if (getNot())
				buffer.append(StringUtil.CHAR_RIGHT_CURVE);
		}
		return buffer;
	}

	public Predicate not(boolean not) {
		this.not = not;
		return this;
	}

	public Predicate not() {
		return not(true);
	}

	public Predicate and() {
		return op(Op.AND);
	}

	public Predicate or() {
		return op(Op.OR);
	}

	public boolean isEmpty() {
		return CoreUtil.isEmpty(conditions) && CoreUtil.isEmpty(predicates);
	}

	public static boolean isEmpty(Predicate p) {
		return p == null || p.isEmpty();
	}

	public static Predicate of(OpExpression... opExprs) {
		Predicate p = new Predicate();
		for (OpExpression opExpr : opExprs) {
			if (opExpr instanceof Predicate)
				p.addPredicate((Predicate) opExpr);
			else if (opExpr instanceof Condition)
				p.addCondition((Condition) opExpr);
			else
				throw new UnsupportException(opExpr.getClass().getName());
		}
		return p;
	}
}
